package dai.core.base

import org.lwjgl.glfw.GLFW
import org.lwjgl.glfw.GLFWErrorCallback
import org.lwjgl.opengl.GL
import org.lwjgl.opengl.GL20C
import org.lwjgl.opengl.GLUtil
import org.lwjgl.system.Callback
import org.lwjgl.system.Configuration
import java.lang.IllegalStateException
import java.util.concurrent.TimeUnit

class Engine(private val window: Window, private val logic: IGameLogic){

    private var frame = 0

    // callback handlers
    var errorCb: GLFWErrorCallback? = null
    var debugProc: Callback? = null

    companion object {
        const val Debug = true
        const val OneFrameTimeMs = 1000f / 60f
        fun nowMs() = TimeUnit.MILLISECONDS.convert(System.nanoTime(), TimeUnit.NANOSECONDS)
    }

    private fun initEnv() {
        if (Debug) {
            Configuration.DEBUG.set(Debug)
            Configuration.DEBUG_LOADER.set(false)
            Configuration.DEBUG_MEMORY_ALLOCATOR.set(false)
            Configuration.DEBUG_STACK.set(false)
        }

        // error callback and init glfw
        errorCb = GLFWErrorCallback.createPrint(System.err)
        if (!GLFW.glfwInit()) {
            throw IllegalStateException("unable to init glfw!")
        }
        // init window
        window.init()

        // set callbacks
        // TODO: Keyboard / Mouse
        GLFW.glfwSetFramebufferSizeCallback(window.winHandler) { winHandle, width, height ->
            window.width = width
            window.height = height
            window.bResize = true
            MyLog.info("new width:${width},height:${height}")
        }

        // set context
        val caps = GL.createCapabilities()
        MyLog.info(caps.toString())

        // opengl debug context
        if (Debug) {
            debugProc = GLUtil.setupDebugMessageCallback(System.out)
        }

        // 开启垂直同步
        GLFW.glfwSwapInterval(1)
        GLFW.glfwShowWindow(window.winHandler)
    }

    private fun end() {
        errorCb?.free()
        debugProc?.free()
    }

    fun start() {
        initEnv()
        MyLog.info("version = ${GL20C.glGetString(GL20C.GL_VERSION)}")
        logic.init(window)
        gameLoop()
        logic.cleanup()
        MyLog.info("before end")
        end()
    }

    private fun gameLoop() {
        var lastFrameTime = nowMs()
        val winHandle = window.winHandler
        while (!GLFW.glfwWindowShouldClose(winHandle)) {
            val frameStart = nowMs()
            val dtMs = frameStart - lastFrameTime
            lastFrameTime = frameStart
            logic.input(window)
            logic.update(dtMs / 1000f)
            logic.render(window)
            window.swap()
            frame++
            val spentMs = nowMs() - frameStart
            if (spentMs < OneFrameTimeMs) {
                Thread.sleep((OneFrameTimeMs - spentMs).toLong())
            }
        }
    }
}
